/* @flow */

import VNode from './vnode'
import { resolveConstructorOptions } from 'core/instance/init'
import { queueActivatedComponent } from 'core/observer/scheduler'
import { createFunctionalComponent } from './create-functional-component'

import {
  warn,
  isDef,
  isUndef,
  isTrue,
  isObject
} from '../util/index'

import {
  resolveAsyncComponent,
  createAsyncPlaceholder,
  extractPropsFromVNodeData
} from './helpers/index'

import {
  callHook,
  activeInstance,
  updateChildComponent,
  activateChildComponent,
  deactivateChildComponent
} from '../instance/lifecycle'

import {
  isRecyclableComponent,
  renderRecyclableComponentTemplate
} from 'weex/runtime/recycle-list/render-component-template'

// inline hooks to be invoked on component VNodes during patch
// patch阶段，在组件vnode上触发的hooks钩子函数
const componentVNodeHooks = {
  // 组件初始化钩子
  init(vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if ( // 初次渲染，并且对于keep-alive为true的vnode，执行prepatch钩子,否则，执行子组件child.mount过程
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      // 通过createComponentInstanceForVnode创建一个Vue子类的实例，并赋值给vnode.componentInstance
      // activeInstance：它的作用就是保持当前上下文的 Vue 实例(表示当前vm的实例, 此时在子组件初始化过程中，就可以确定父子关系,即$children, $parent的关系)
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // 挂载子组件，即子组件初始化。hydrating为服务端渲染，所以此时为:child.$mount(undefined, hydrating);
      // 子组件自己接管$mount挂载过程, 即子组件挂载时，$el为undefined
      // 当整个子组件mount完成后，生成的dom元素，赋值给vm.$el
      // 当vnode的init钩子执行完成后，返回到'/vdom/patch.js createComponent'方法(240行)，执行initComponent->insert，将本身的el插入到父节点中
      // 整个子组件挂载完成后，触发vnode的insert钩子 -> 触发组件的'mounted'钩子
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
  // 组件更新钩子
  prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    // options取值：参考createComponent函数(109行)中new VNode(227行)时传递componentOptions值
    const options = vnode.componentOptions
    // componentInstance: 参考componentVNodeHooks.init钩子，初始化子组件时，给vnode赋的值
    const child = vnode.componentInstance = oldVnode.componentInstance
    // 更新子组件: 包括props、listeners
    // 为什么说 Vue 的响应式更新精确到组件级别？：https://juejin.cn/post/6844904113432444942
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },
  // patch完成后，触发componentVnodeHooks的insert钩子，此时子组件的mounted钩子触发
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      // 子组件调用mounted钩子，子组件挂载完成(子组件mounted完成)
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },
  // 组件销毁的钩子
  destroy (vnode: MountedComponentVNode) {
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy()
      } else {
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}
// hooksToMerge: [init, prepatch, insert, destroy]
const hooksToMerge = Object.keys(componentVNodeHooks)
// 创建组件vnode（组件类型的vnode）
/**
 * 1、构造子类构造函数：baseCtor.extend
 * 2、安装组件钩子函数：installComponentHooks： componentVnodeHooks的init、prepatch、insert、destroy钩子
 * 3、实例化组件vnode
 */
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }
  // $options._base就是Vue构造函数
  const baseCtor = context.$options._base
  // 1) 构造Vue子类的构造函数，此时Ctor为Sub子类
  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {  // 如果Ctor是对象，则通过Vue.extend改造为Sub子类
    // Vue.extend(Ctor), 返回的Ctor，就是一个Sub子类，继承自Vue,代码位置：src/core/global-api/extend.js
    Ctor = baseCtor.extend(Ctor)
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component 异步组件vnode
  /**
   * 异步组件: 基本流程为： 创建一个空的占位符节点 -> resolve返回真实组件后，强制更新组件 -> 走真实组件渲染流程
   * 1、通过resolveAsyncComponent方法，解析异步组件
   *    - 第一次返回的Ctor为undefined
   *    - 如果Ctor为undefined，则创建一个空的注释节点渲染到页面中
   * 2、resolve成功后，将返回结果通过Vue.extenf包装为新的Ctor(真实组件的Ctor)，同时赋值给asyncFactory.resolved
   *    - 然后调用vm.$forceUpdate，强制更新组件，重新走组件的渲染流程
   *    - 此时执行resolveAsyncComponent时，由于asyncFactory.resolved存在，则直接返回真实组件的Ctor
   *    - 走真实组件的渲染流程
   */
  let asyncFactory
  if (isUndef(Ctor.cid)) { // 只有通过Vue.extend创建的构造函数，才有cid
    asyncFactory = Ctor
    // 2、解析定义的异步函数Ctor(注意，此时Ctor是我们定义的一个函数，它返回一个Promise)，参考:https://cn.vuejs.org/v2/guide/components-dynamic-async.html#异步组件
    // baseCtor指的是Vue构造函数
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      // 1、创建异步组件占位符(注释节点)
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

  data = data || {}

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)

  // transform component v-model data into props & events
  // 自定义组件v-model
  /**
   *  <my v-model="val"></my> ,编译后结果为：
   * function render(){with(this){return _c("my", { model:{ value:(val), callback:function($$v){val=$$v}, expression:"val" }})}};
   */
  if (isDef(data.model)) {
    // 处理组件v-model：转化组件的v-model数据到props和event是中
    // 默认是prop为'value'，event为'input'，但是可以通过组件options的model选项来修改默认值: model: { prop: '', event: ''}
    transformModel(Ctor.options, data)
  }

  // extract props 从data中提取propsData(父组件传递的)
  /**
   * data: 来自于"src\core\vdom\create-element.js(53行)"中_createElement函数的参数，数据对象data。
   *       data中的pros即为传递props数据，参考：https://cn.vuejs.org/v2/guide/render-function.html#深入数据对象
   * Ctor：Sub子类
   * tag：组件tag标签
   * 注意：从data中抽离$attrs、props：$attrs是所有的属性，props是用户申明的属性。如果用户在props中已经声明过某个属性，那么它将不会出现在$attrs中。
   */
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // functional component 函数式组件
  // 函数式组件的data上，没有vnode的hook钩子
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners

  // 组件事件处理：绑定在组件上的监听函数(组件的自定义事件)，存放到vnode的componentOptions对象中的listeners属性上
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.

  // 仅用于组件，用于监听'原生事件'，而不是组件内部使用
  data.on = data.nativeOn

  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot

    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // install component management hooks onto the placeholder node
  // 2）安装data.hook钩子函数(用于后续调用hook.init，进行组件初始化操作)，主要是componentVnodeHooks的钩子，包括init、prepatch、insert、destroy
  /**
   * 安装vnode钩子：把组件vnode的componentVnodeHooks的钩子，合并到data.hook中，在vnode执行patch的过程中执行相关钩子函数
   * 注意：在合并过程中，如果某个时机的钩子已经存在(后执行)，则执行mergeHook合并，最终执行时，这两个钩子依次执行。
   */
  installComponentHooks(data); // vnode声明周期钩子

  // return a placeholder vnode
  const name = Ctor.options.name || tag
  // 3） 创建组件vnode
  // 注意：vnode的'componentOptions'选项（第七个参数）
  /**
   * Ctor: Sub子类
   * propsData：从父组件传递的props数据，
   *    组件经过编译后，最终转为vnode时，将props存放到componentOptions选项中的propsData中，例如：<my-comp a=1></my-comp> -> ast语法树 -> vnode
   * listeners：绑定到组件上的事件监听，其值来源于createElement数据对象中的data.on
   *    
   * tag：组件标签tag
   * children：组件的children(vnode数组)
   */
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )

  // Weex specific: invoke recycle-list optimized @render function for
  // extracting cell-slot template.
  // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  /* istanbul ignore if */
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }

  return vnode
}

export function createComponentInstanceForVnode (
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any, // activeInstance in lifecycle state
): Component {
  // vm._init函数中,对options的判断,参考代码：/src/core/instance/init.js，32行，对options的判断
  const options: InternalComponentOptions = {
    _isComponent: true, // 标记这是一个component
    _parentVnode: vnode,
    parent  // 父组件vm实例, 用于在_init函数阶段，执行initLifecycle时，确认组件的父子关系
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  // vnode.componentOptions.Ctor就是子组件的构造函数Sub
  /**
   * 因为：在调用_createElement创建vnode时，如果tag是一个component,会调用createComponent(vdom/create-element.js,126行)方法来创建组件vnode
   * 这个函数内部，Ctor = baseCtor.extend(Ctor)，即Ctor是通过Vue.extend(global-api/extend.js,19行)返回的子类构造函数Sub。
   * 然后调用new VNode()生成vnode,传入的componentOptions为：{ Ctor, propsData, listeners, tag, children }
   * 所以vnode.componentOptions.Ctor就是Sub构造函数
   */
  // 相当于new Sub(options)，注意此时options比较特殊(参考组件_init时，options的合并策略)
  return new vnode.componentOptions.Ctor(options)
}
// hooksToMerge： init、prepatch、insert、destroy4个钩子函数
function installComponentHooks (data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    const existing = hooks[key]
    const toMerge = componentVNodeHooks[key]
    if (existing !== toMerge && !(existing && existing._merged)) {
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
    }
  }
}

function mergeHook (f1: any, f2: any): Function {
  const merged = (a, b) => {
    // flow complains about extra args which is why we use any
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}

// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data: any) {
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
  ;(data.attrs || (data.attrs = {}))[prop] = data.model.value
  const on = data.on || (data.on = {})
  const existing = on[event]
  const callback = data.model.callback
  if (isDef(existing)) {
    if (
      Array.isArray(existing)
        ? existing.indexOf(callback) === -1
        : existing !== callback
    ) {
      on[event] = [callback].concat(existing)
    }
  } else {
    on[event] = callback
  }
}
